# -*- coding: utf-8 -*-

'''
Created on 26/11/2011

@author: Luis M. Ugalde
www.forondarena.net

Copyright © 2011 Luis M. Ugalde
Licensed under the terms of the MIT License
(see LICENSE file for details)

This software can't be considered to be even in Alpha state. It still needs a lot of work.

Remember that abstract interfaces have a different syntax in python 3:
 You need the: from abc...
                class AbstractReader(metaclass = ABCMeta)
 and you dont need the __metaclass__ ...
'''


from abc import abstractmethod, ABCMeta
import zmq
from parsers.parserexception import ParserException


class AbstractParser(object):
    '''
    Generic class to be inherited by every parser implementation.
    Parsers must implement all defined abstract methods. Raise TypeError otherwise
    '''


    __metaclass__ = ABCMeta


    def __init__(self, *args, **kwargs):
        self.context = zmq.Context()
        self.poller = zmq.Poller()
        self.readerconnectaddresses = []
        self.readerconnectsockets = []
        self.readerconnectnotifyaddresses = []
        self.readerconnectnotifysocket = None
    
    def setHelper(self, helperfile = None):
        self.filename = helperfile
        print "{}: Opening file for writing: {}".format(self.__class__.__name__, self.filename)
        try:
            self.file = open(self.filename, 'a')
        except (IOError, TypeError):
            print "{}: Cant open {} for writing. Unparseable lines will not be stored".format(self.__class__.__name__, self.filename)
            self.file = None


    def closeHelper(self):
        try:
            self.file.close()
        except (IOError, AttributeError):
            print "{}: Cant close {}. Were leaving, anyway".format(self.__class__.__name__, self.filename)
        finally:
            self.file = None


    def useHelper(self):
        return True if self.file != None else False
    
    
    def writeHelper(self, line = None):
        try:
            self.file.write(line)
        except IOError:
            if __debug__:
                print "{}: Something happened writing to {}. Closing the file".format(self.__class__.__name__, self.filename)
            self.closeHelper()


    def setSource(self, proto = "inproc", host = "reader", port = None):
        '''
        The source for a parser is always a zeromq socket created by a reader.
        '''
        
        port = str(port)
        self.readerconnectaddresses.append(proto + '://' + host + ':' + port if port else proto + '://' + host)
        print "{}: Reader socket is {}".format(self.__class__.__name__, proto + '://' + host + ':' + port if port else proto + '://' + host)


    def connectSources(self):
        '''
        Connect the previously defined sources to receive data from readers.
        '''
        try:
            i = 0
            while i < len(self.readerconnectaddresses):
                print "Connecting  to {}.".format(self.readerconnectaddresses[i])
                tmpsocket = self.context.socket(zmq.PULL)
                tmpsocket.connect(self.readerconnectaddresses[i])
                self.readerconnectsockets.append(tmpsocket)
                self.poller.register(self.readerconnectsockets[i], zmq.POLLIN)
                i += 1
        except zmq.ZMQError:
            if __debug__: print "Could not connect to reader source sockets. Trying to close everything."
            self.closeSources()
            raise ParserException('Could not connect to reader sources.')


    def closeSources(self):
        '''
        Close the open reader sockets.
        '''
        
        for i in self.readerconnectsockets:
            self.poller.unregister(i)
            i.setsockopt(zmq.LINGER, 0)
            i.close()


    def setNotifySource(self, proto = "inproc", host = "notifyreader", port = None):
        '''
        Set up a socket to receive broadcast from available readers.
        '''
        
        port = str(port)
        self.readerconnectnotifyaddresses.append(proto + '://' + host + ':' + port if port else proto + '://' + host)
        print "{}: Reader notify socket is {}".format(self.__class__.__name__, proto + '://' + host + ':' + port if port else proto + '://' + host)


    def connectNotifySources(self):
        '''
        Connect the previously defined sources to receive broadcasts from readers.
        '''
        
        try:
            for i in self.readerconnectnotifyaddresses:
                print "Connecting  to {}.".format(i)
                self.readerconnectnotifysocket = self.context.socket(zmq.SUB)
                self.readerconnectnotifysocket.connect(i)
        
            self.readerconnectnotifysocket.setsockopt(zmq.SUBSCRIBE, "")
            self.poller.register(self.readerconnectnotifysocket, zmq.POLLIN)
        except zmq.ZMQError:
            if __debug__: print "Could not connect to reader source notify sockets. Trying to close everything."
            self.closeNotifySources()
            raise ParserException('Could not connect to reader notify sources.')


    def closeNotifySources(self):
        '''
        Close the open reader sockets.
        '''
        
        self.poller.unregister(self.readerconnectnotifysocket)
        self.readerconnectnotifysocket.setsockopt(zmq.LINGER, 0)
        self.readerconnectnotifysocket.close()


    def setBindParser(self, proto = "inproc", host = "parser", port = None):
        '''
        Set up a zeromq socket address to send parsed lines to state stores.
        '''
        
        port = str(port)
        self.parserbindaddress = proto + '://' + host + ':' + port if port else proto + '://' + host
        print "{}: Parser bind socket is {}".format(self.__class__.__name__, self.parserbindaddress) 

        
    def bindParser(self):
        '''
        Bind the defined zeromq socket.
        '''
        
        try:
            self.parserbindsocket = self.context.socket(zmq.PUSH)
            self.parserbindsocket.bind(self.parserbindaddress)
        except zmq.ZMQError:
            tmperror = "Unable to bind " + self.parserbindaddress
            raise ParserException(tmperror)


    def closeBindParser(self):
        '''
        Close the defined zeromq socket.
        '''

        self.parserbindsocket.setsockopt(zmq.LINGER, 0)
        self.parserbindsocket.close()


    def setBindNotifyParser(self, proto = "inproc", host = "notifyparser", port = None):
        '''
        Set up a zeromq socket to broadcast notifications to State Stores.
        '''
        
        port = str(port)
        self.parserbindnotifyaddress = proto + '://' + host + ':' + port if port else proto + '://' + host
        print "{}: Parser bind socket is {}".format(self.__class__.__name__, self.parserbindnotifyaddress)

        
    def bindNotifyParser(self):
        '''
        Bind the defined zeromq socket.
        '''

        try:
            self.parserbindnotifysocket = self.context.socket(zmq.PUB)
            self.parserbindnotifysocket.bind(self.parserbindnotifyaddress)
        except zmq.ZMQError:
            tmperror = "Unable to bind " + self.parserbindnotifyaddress
            raise ParserException(tmperror)


    def closeBindNotifyParser(self):
        '''
        Close the defined zeromq socket.
        '''

        self.parserbindnotifysocket.setsockopt(zmq.LINGER, 0)
        self.parserbindnotifysocket.close()


    def closeContext(self):
        self.context.term()


    @abstractmethod
    def parseLines(self, *args, **kwargs):
        pass


if __name__ == '__main__':
    print "Please, do not run this"
